<!DOCTYPE html>
<html manifest="taskwall.manifest">
<head><title>Task Wall for iPhone - See all your Google Tasks in one place</title>
<link rel="shortcut icon" href="https://mail.google.com/tasks/res/favicon.png">
<link rel="apple-touch-icon" href="todoist57.png">
<link rel="apple-touch-icon" sizes="114x114" href="todoist114.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script>
document.title = "Tasks";

function $(s) {
  return document.getElementById(s);
}

var ajax = {
  counter: 0,
  f: function(o) {
    ajax["n" + this.i] = null;
    this.cb(o);
  }
};

var taskwall = {
  oauth: "",
  goog: "https://accounts.google.com/o/oauth2/auth?client_id=145596226637.apps.googleusercontent.com&redirect_uri="+encodeURIComponent(location.href)+"&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Ftasks&response_type=token",

  onload: function() {
    var token = /\baccess_token=([^&]+)(&|$)/i.exec(location.href);

    if(token && location.href.indexOf("state=" + this.cookie("csrf")) > 0) {
      this.oauth = this.cookie("googletasktoken", token[1]);

      //silent login via iframe
      if(top && top != window) return top.postMessage('{"oauth":"ok"}', location.href);

      this.getProjs();

    } else if(location.href.indexOf("error=access_denied") > 0) {
      alert("Error: Could not get your tasks because access was denied.");

    } else {
      this.oauth = this.oauth || this.cookie("googletasktoken") || null;
    }

    var x = this.cookie("soon");
    if(x) $("q").value = $("startq").value = x;

    x = this.cookie("twbg");
    if(x) document.body.className = x;

    this.updateView();

    this.cookie("csrf", Math.random());
    if(!this.oauth) $("intro").style.display = "";
  },

  cookie: function(n, v) {
    if(v == {}.v) return window.localStorage[n];
    window.localStorage[n] = v;
    return v;
  },

  ajax: function(url, param, cb) {
    var i = ajax.counter++;
    ajax["n" + i] = {cb:cb, i:i, f:ajax.f};

    url = "https://www.googleapis.com/tasks/v1" + url + "?callback=ajax.n" + i + ".f&prettyPrint=false&access_token="+this.oauth + "&" + param;
    document.getElementsByTagName("head")[0].appendChild(document.createElement("script")).src = url;
  },

  ajax2: function(method, url, body, cb) {
    this.ajax2prep();

    var q = this._ajax2Que;
    var nhr = "googleapis.newHttpRequest";
    q.push([{
      s:"makeHttpRequests", f:"..", c:++ajax.counter, t:"1973269718", l:false,
      a:[[{
        method:nhr, id:nhr, key:nhr, headers:{"X-ClientDetails":""},
        params: {
          url:url, body:body,
          httpMethod:method, userId:null, groupId:null, applicationName:null, clientVersion:"0.1",
          headers:{"X-Javascript-User-Agent":"google-api-gwt-client/0.1-alpha", "Content-Type":"application/json"}
        }
      }]]
    }, cb]);

    if(this._ajax2ok == 3 && q.length == 1) {
      frames["apiproxy_197326"].postMessage(this.obj2json(q[0][0]), "https://www.googleapis.com");
    }
  },

  ajax2prep: function() {
    if(!this._ajax2ok) {
      this._ajax2ok = 1;
      var n = document.createElement("span");
      n.innerHTML = "<iframe name='apiproxy_197326' id='apiproxy_197326' src='" +
        "https://www.googleapis.com/static/proxy.html#parent=" + encodeURIComponent(location.href) +"&amp;rpctoken=1973269718'></iframe>" +
        "<iframe src='" + this.goog + "&state=" + this.cookie("csrf") + "'></iframe>";
      document.body.appendChild(n).firstChild;
      addEventListener("message", this.onMessage, false);
      this._ajax2Que = [];
    }
  },

  obj2json: function(o) {
    if(window.JSON) return JSON.stringify(o);
    if(o instanceof Array) {
      var arr = [];
      for(var i=0; i<o.length; i++) arr[i] = this.obj2json(o[i]);
      return "[" + arr.join(",") + "]";
    }
    if(o instanceof Date) return '"' + o.toISOString() + '"';
    switch(typeof o) {
      case "number": return o;
      case "string": return '"' + o.replace(/"/g,'\"') + '"';
      case "boolean": !!o;
      case "object":
        var arr = [];
        for(var i in o) arr.push('"' + i + '":' + this.obj2json(o[i]));
        return arr.length ? "{" + arr.join(",") + "}" : "null";
    }
  },

  onMessage: function(e) {
    if(!/^https?:\/\/(simpleajax.googlecode.com|www.googleapis.com)$/.test(e.origin)) return;

    e = window.JSON ? JSON.parse(e.data) : eval("(" + e.data + ")");

    if(e.s == "ready:1973269718") {
      taskwall._ajax2ok += 1;

    } else if(e.s == "__cb") {
      var obj = window.JSON ? JSON.parse(e.a[1]) : eval("(" + e.a[1] + ")");
      obj = obj["googleapis.newHttpRequest"];
      var code = obj.data.status;

      if(code >= 200 && code < 300) {
        obj = obj.data.body;
        (taskwall._ajax2Que.pop()[1])(window.JSON ? JSON.parse(obj) : eval("(" + obj + ")"));
      } else taskwall.handleErr({error:{code:code, message:obj.data.statusText}});

    } else if(e.oauth == "ok") {
      taskwall.oauth = taskwall.cookie("googletasktoken");
      taskwall._ajax2ok += 1;

    } else return;

    if(taskwall._ajax2ok == 3) {
      e = document.getElementsByClassName("ajax2wait");
      for(var i=e.length - 1; i>=0; i--) e[i].className = "";

      if(taskwall._ajax2Que.length > 0) frames["apiproxy_197326"].postMessage(
        taskwall.obj2json(taskwall._ajax2Que[taskwall._ajax2Que.length - 1][0]),
        "https://www.googleapis.com"
      );
    }
  },

  handleErr: function(r) {
    if(r.error.code == 401) {
      this.login();
    } else alert(r.error.code + " " + r.error.message);
  },

  login: function() {
    $("upd").parentNode.innerHTML = "Login...";
    location.href = this.goog + "&state=" + this.cookie("csrf");
  },

  getProjs: function() {
    var dis = this;

    if(!this.oauth) return this.login();

    this.ajax("/users/@me/lists", "fields=items(id,title),nextPageToken", function(r) {
      if(r.error) return dis.handleErr(r);

      r = r.items;
      dis.countdown = r.length;
      for(var i=0; i<r.length; i++) dis.getTasks(r[i].id);

      dis.db.transaction(function(t) {
        t.executeSql("DELETE FROM projs", []);

        for(var i=0; i<r.length; i++) {
          t.executeSql("INSERT INTO projs (id, title) VALUES (?, ?)", [r[i].id, r[i].title]);
        }
      });
    });

    $("upd").style.WebkitTransform = "rotate(-180deg)";
  },

  getTasks: function(proj) {
    var dis = this;

    if(!this.oauth) return this.login();

    this.ajax("/lists/" + proj + "/tasks", "showCompleted=false&fields=items(due%2Cid%2Cparent%2Cposition%2Cstatus%2Ctitle)%2CnextPageToken", function(r) {
      if(r.error) {
        dis.countdown--;
        return dis.handleErr(r);
      }

      r = r.items;

      var arr, subtree = {root:[]};

      if(r) for(var i=0, len=r.length; i<len; i++) {

        arr = subtree[r[i].parent || "root"];
        if(!arr) arr = subtree[r[i].parent || "root"] = [];

        if(!r[i].due) {
          arr.push(r[i]);

        } else {
          var j = 0;
          while(j < arr.length && r[i].due > arr[j].due) j++;
          arr.splice(j, 0, r[i]);
        }
      }

      r = subtree["root"];

      var i = -1;
      while(++i < r.length) {
        r[i].date_order = i;

        arr = subtree[r[i].id];
        if(arr) {
          arr.unshift(i + 1, 0);
          r.splice.apply(r, arr);
        }
      }

      dis.db.transaction(function(t) {
        t.executeSql("UPDATE tasks SET proj=NULL WHERE proj=?", [proj]);

        for(var i=0, len=r.length; i<len; i++) {
          if(r[i].title) t.executeSql(
            "INSERT OR REPLACE INTO tasks (id, title, parent, due, position, date_order, proj, flag) VALUES (?, ?, ?, ?, ?, ?, ?, (SELECT flag FROM tasks WHERE id=?))", 
            [r[i].id, r[i].title.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"), r[i].parent, r[i].due, r[i].position, r[i].date_order, proj, r[i].id]
          );
        }

        t.executeSql("DELETE FROM tasks WHERE proj=NULL", [], function(t, r) {
          dis.countdown--;
          $("upd").style.WebkitTransform = "rotate(-" + dis.countdown + "0deg)";
          if(dis.countdown == 0) {
            if(location.href.indexOf("#") > 0) location.replace(location.href.match(/^[^#]+/));
            dis.updateView();
          }
        });

      });
    });
  },

  addTask: function(e, t, proj, parent) {
    if(!window.postMessage || !this.oauth || e.ctrlKey || e.metaKey || e.shiftKey) return;
    this.ajax2prep();

    var n = document.createElement("tr");
    n.innerHTML = "<td colspan=3 class='edit'><form onsubmit='taskwall.addTaskForm(this);return false;'><div><textarea name=txt " +
      "placeholder='Task' autocapitalize=off></textarea></div><a href='javascript:void(0);' onclick='return taskwall.hideTaskForm(this);'>&times;</a>" +
      "<input name=due type=text placeholder='today, tue, +2' autocapitalize=off autocorrect=off><input name=a type=submit " + 
      (this._ajax2ok != 3 ? "class='ajax2wait' " : "") +
      "value=' Add '><input type=hidden name=proj value='" +
      proj + "'><input type=hidden name=parent value='" + (parent||"") + "'></form></td>";

    var p = parent ? t.parentNode : $(proj).getElementsByTagName("th")[0].parentNode;
    p.parentNode.insertBefore(n, p.nextSibling);

    if("ontouchend" in document.body) {
      t.onclick = function() {
        n.getElementsByTagName('textarea')[0].focus();
        t.onclick = null;
      };
    } else n.getElementsByTagName('textarea')[0].focus();
  },

  hideTaskForm: function(a) {
    a = a.parentNode.parentNode.parentNode;
    if(a.getElementsByTagName("textarea")[0].value && !confirm("Discard?")) return;
    a.parentNode.removeChild(a);
    return false;
  },

  addTaskForm: function(f) {
    var dis = this;

    if(this._ajax2ok != 3) return;
    if(!f.txt.value) return dis.hideTaskForm({parentNode:f});
    if(f.due.value && !dis.strToDate(f.due.value)) return alert("Invalid date. Use 'd/m/yyyy'.");

    var proj = f.proj.value;
    f.a.disabled = true;
    this.ajax2(
      "POST",
      "/tasks/v1/lists/" + proj + "/tasks?prettyPrint=false&access_token=" + dis.oauth + (f.parent.value ? "&parent=" + f.parent.value : ""),
      {title:f.txt.value, parent:f.parent.value||null, due:dis.strToDate(f.due.value)},
      function(r) {
        f = f.parentNode;
        f.parentNode.removeChild(f);

        dis.db.transaction(function(t) {
          if(r.title) t.executeSql(
            "INSERT INTO tasks (id, title, parent, due, position, date_order, proj, flag) VALUES (?, ?, ?, ?, ?, ?, ?, 1)", 
            [r.id, r.title.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"), r.parent, r.due, r.position, 0, proj],
            function(r) {dis.updateView();}
          );
        });
      }
    );
  },

  strToDate: function(s) {
    s = s.toLowerCase().replace(/^\s+|\s+$/g, "").replace(/\s\s+|[\.-]/g, " ");
    var d = new Date();
    d.setHours(23); d.setMinutes(59); d.setSeconds(59);

    if(/^(tom|tomorrow)/.test(s)) {
      d.setDate(d.getDate()+1);
      return d;

    } else if(/^(tod|today)/.test(s)) {
      return d;

    } else if(s.indexOf("+") == 0) {
      d.setDate(d.getDate() + parseInt(s));
      return d;
    }

    var reg;
    if((reg = s.match(/^(\d{1,2})[- \.\/](\d{1,2}|[a-z]{3})(?:[- \.\/](\d{4}))?$/))) {
      var months = {"jan":1, "feb":2, "mar":3, "apr":4, "may":5, "jun":6, "jul":7, "aug":8, "sep":9, "oct":10, "nov":11, "dec":12};
      var now = d.getTime();
      d = new Date(reg[3]||d.getFullYear(), ((reg[2]*1||months[reg[2]]) - 1)||d.getMonth(), reg[1]||d.getDate(), 23, 59, 59);
      if(!reg[3] && d.getTime() - now < 0) d.setYear(d.getFullYear() + 1);
      return d;
    }

    var days = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
    for(var i=0; i<days.length; i++) {
      if(s.indexOf(days[i]) >= 0) {
        d.setDate(d.getDate() + ((i + 1 - d.getDay() + 7) % 7) + (s.indexOf("next ") == 0 ? 7 : 0));
        return d;
      }
    }
    return null;
  },

  updateView: function(s) {
    if(document.getElementsByTagName("textarea").length) return alert("You have unsaved tasks. Update postponed until all tasks are saved.");

    var dis = this;
    var pad = function(n) {return n < 10 ? "0" + n : n;};

    if(typeof s == "string" || typeof s == "number") $("q").value = s;

    var filter = $("q").value;
    var filterSql = "", highlight = false;

    if(filter == "@*") {
      filterSql = "AND (flag == 2 OR flag == 4) ";
      filter = null;

    } else if(filter == "@#") {
      filterSql = "AND (flag == 3 OR flag == 4) ";
      filter = null;

    } else if(filter.substr(0, 2) == "@~") {
      filterSql = "AND projs.title LIKE ? ESCAPE \"!\" ";
      filter = filter.substr(2).toLowerCase().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");

    } else if(/^@(\d+)$/.test(filter)) {
      filter = /^@(\d+)$/.exec(filter)[1];
      var d = new Date();
      d.setTime(d.getTime() + filter*24*60*60000);
      filterSql = "AND due <= '" + d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) + "T00:00:00.000Z' ";
      filter = null;

    } else if(filter) {
      filter = filter.toLowerCase().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
      filterSql = "AND tasks.title LIKE ? ESCAPE \"!\" ";
      highlight = true;
    }

    var touch = ("ontouchend" in document.body);

    this.db.readTransaction(function(t) {

      t.executeSql("SELECT id, title FROM projs ORDER BY title", [], function(t, r) {
        r = r.rows;
        var n, html = [];
        for(var i=0, len=r.length; i<len; i++) {
          n = r.item(i);
          html.push("<span class='group'><a href='javascript:taskwall.updateView(\"@~"+n.title+"\");'>" + n.title + "</a></span> ");
        }
        if(html.length) html.push("<span class='group'><a href='javascript:taskwall.updateView(\"\");'>All</a></span>");
        html = html.join("");
        $("projects").innerHTML = touch ? html.replace(/ href='/g, " href='javascript:void(0);' ontouchend='if(!isTouch)return;isTouch=0;") : html;
      });

      t.executeSql("SELECT *, tasks.id AS id, tasks.title AS h2, projs.title AS h1 FROM tasks, projs WHERE tasks.proj=projs.id " + filterSql + "ORDER BY projs.title, date_order",
        (filter ? ["%"+filter.replace(/%/g,"!%").replace(/_/g,"!_")+"%"] : []),
        function(t, r) {
        r = r.rows;

        var i = 0, proj, lastProj = this, n, stack, indent = 0, h2, id, parent, due, overdue, flag, lastn = {id:this};

        var tod = new Date();
        var year = tod.getFullYear();
        var tom = new Date(tod.getTime() + 24*60*60000);
        tod = year + "-" + pad(tod.getMonth() + 1) + "-" + pad(tod.getDate());
        tom = tom.getFullYear() + "-" + pad(tom.getMonth() + 1) + "-" + pad(tom.getDate());

        var html = [], errBounds = true;
        try {
          while((n = r.item(i++))) {
            errBounds = false;
            proj = n.proj;
            if(proj != lastProj) html.push("</table><table cellspacing=0 border=0 id='" + proj + "'><tr><th colspan=3 onclick='taskwall.addTask(event, this, \"" + proj + "\");'>" + n.h1 + "</th></tr>");
            lastProj = proj;

            if(n.parent == "undefined" || !n.parent) {
              stack = [];
              indent = 0;

            } else if(n.parent == lastn.id) {
              stack.unshift(n.parent);
              indent++;

            } else if(indent > 0 && n.parent != stack[0]) {
              while(stack.shift() != n.parent && --indent > 0);
            }

            lastn = n;

            h2 = n.h2;
            if(highlight) h2 = h2.toLowerCase().replace(filter, "<u>" + filter + "</u>");

            id = n.id;
            due = n.due;
            if(due) due = due.substr(0, 10);
            flag = n.flag;
            parent = n.parent;

            overdue = due < tod ? "overdue" : "";
            due = (!due || due == "undefined") ? "" : due == tod ? "today" : due == tom ? "tom" : (due.substr(0, 4) == year ? "" : due.substr(0, 4) + "<span>/</span>") + due.substr(5, 2)*1 + "<span>/</span>" + due.substr(8, 2)*1;
            html.push(
              "<tr><td onclick='taskwall.check(this,0,\"" + id + "\");' class='f" + flag + "'><b class='fl'>&#9733;</b><b>&sdot;</b>" +
              "</td><td id='" + id + "' class='ind" + indent + "' onclick='taskwall.addTask(event, this, \"" + proj + 
              "\", this.id);'><span class='" + overdue + "'>" + due + "</span>" + h2 +
              "</td><td onclick='taskwall.check(this,1,\"" + id + "\");' class='f" + flag + 
              "'><b class='fr'>&hearts;</b><b>&sdot;</b></td></tr>"
            );
            errBounds = true;
          }
        } catch(e) {if(!errBounds) throw e;}

        html.push(!html.length ? "<h3 style='text-align:center'>No Tasks</h3>" : "</table>");
        html = html.join("");
        $("main").innerHTML = touch ? html.replace(/ onclick='/g, " onclick='void(0);' ontouchend='if(!isTouch)return;isTouch=0;") : html;
      });

    });
  },

  check: function(node, dir, id) {
    //flag: 1=empty, 2=left, 3=right, 4=left+right
    var options = [[null, 2, 1, 4, 3], [null, 3, 4, 1, 2]];

    var flag = options[dir][node.className.charAt(1)*1];
    node.className = "f" + flag;

    this.db.transaction(function(t) {
      t.executeSql("UPDATE tasks SET flag=? WHERE id=?", [flag, id]);
    });
  },

  logout: function() {
    this.cookie("googletasktoken", "");
    this.db.transaction(function(t) {
      t.executeSql('DROP TABLE tasks', []);
      t.executeSql('DROP TABLE projs', []);
    });
    alert("Logout done, but note you will still be logged in at Google.com.");
    location.reload();
  },

  db: {
    readTransaction: function(a, b, c) {
      this.transaction(a, b, c);
    },

    transaction: function(sql, err, ok) {
      var noop = new Function("");
      var queue = [[sql, err, ok]];
      var dis = taskwall;

      this.transaction = function(a, b, c) {
        queue.push([a, b, c]);
      };

      var fail = function(e) {
        dis.db.transaction = noop;
        for(var i=0; i<queue.length; i++) {
          (queue[i][1]||noop)(e);
        }
      };

      try {
        var db = window.openDatabase("google-tasks-r", "1.0", "Google Tasks", 5*1024*1024);
        db.transaction(
          function(t) {
            t.executeSql("CREATE TABLE IF NOT EXISTS tasks (id TEXT UNIQUE NOT NULL, title TEXT, parent TEXT, due DATE, position TEXT, date_order INTEGER, flag TINYINT NOT NULL DEFAULT 1, proj TEXT)");
            t.executeSql("CREATE TABLE IF NOT EXISTS projs (id TEXT UNIQUE NOT NULL, title TEXT)");

          }, fail, function() {//ok
            for(var i=0; i<queue.length; i++) {
              db.transaction(queue[i][0], queue[i][1] || noop, queue[i][2] || noop);
            }
            dis.db = db;
          }
        );
      } catch(e) {
        fail(e);
      }
    }
  }
};
</script>
<style>
body {margin:0; padding:0; background:#fff; color:#000; -webkit-tap-highlight-color:rgba(0,0,0,0); -webkit-text-size-adjust:100%; overflow-y:scroll}
body.blk {background:#000; color:#fff}
body, input, textarea, h1 {font:11pt Helvetica,Calibri,Tahoma}
#upd {-webkit-transition:-webkit-transform 0.5s; display:inline-block}
iframe {position:absolute; width:1px; height:1px; top:-100px}
.menu {margin:3px 0}
.group {background:#ddd; border-radius:1em; padding:0.45em; margin:0 0.3em; line-height:2.55em; font-size:80%; font-weight:bold; white-space:nowrap}
.group a {padding:0.7em; text-decoration:none; color:#000; text-shadow:-1px 1px 0 #eee; -webkit-touch-callout:none}
.blk .group {background:#333}
.blk .group a {color:#ddd; text-shadow:1px 1px 0 #333}
.blk input[type="text"], .blk textarea {background:#000; color:#fff}
table {width:100%; border-collapse:collapse}
#main {border-bottom:1px solid #e2e2e2}
.blk #main {border-bottom-color:#333}
th {font-size:120%; font-weight:bold; padding:0.5em; -webkit-animation:flash_wht 0.6s ease-out}
.blk th {-webkit-animation:flash_blk 0.6s ease-out}
td {vertical-align:top; padding:0.3em 0.1em}
th, td {text-align:left; border-top:1px solid #e2e2e2; word-wrap:break-word; word-break:break-all}
.blk th, .blk td {border-top-color:#333}
tr:nth-child(even) {color:#777}
.blk tr:nth-child(even) {color:#999}
td:first-child, td:last-child {width:1.3em; padding:0; font-size:150%; text-align:center; font-weight:bold; cursor:pointer; color:#ccc}
.blk td:first-child, .blk td:last-child {color:#999}
td > span {float:right; border-radius:10px; padding:0 0.4em; background:#eee; color:#000; font-size:90%}
.blk td > span {background:#333; color:#fff}
td > span.overdue {background:#fbb}
.blk td > span.overdue {background:#800}
td > span > span {padding:0 0.2em}
.ind1 {padding-left:20px}
.ind2 {padding-left:40px}
.ind3 {padding-left:60px}
.ind4 {padding-left:80px}
.ind5 {padding-left:100px}
.fl, .fr {display:none}
.f4 .fl+b, .f4 .fr+b, .f2 .fl+b, .f3 .fr+b {display:none}
.f4 .fl, .f4 .fr, .f2 .fl, .f3 .fr {display:inline; color:#888}
td > u {text-decoration:none; background:#cc0; color:#fff; padding:0 0.1em; -moz-border-radius:0.3em; border-radius:0.3em}
.blk td > u {background:#ff4; color:#000}
.inputbox input {-webkit-appearance:none; border-radius:10px; border:0; padding:0.1em 0.5em; margin:0; font-family:arial}
td.edit {background:#ddd; cursor:default; -webkit-animation:slidein 0.3s}
.blk .edit {background:#333}
form {margin:2%}
textarea {height:5em; width:98%; margin:0 auto; border:0; word-break:normal}
.edit input[name="due"] {width:50%; min-width:4em; margin-right:1%; border:0}
.edit .ajax2wait {display:none}
.edit a {font-size:70%; color:#aaa; padding:0 1%; margin-right:4%}
@-webkit-keyframes flash_wht {10%{background:#eee}}
@-webkit-keyframes flash_blk {10%{background:#333}}
@-webkit-keyframes slidein {from{-webkit-transform:translateX(-70px); opacity:0}}
@media screen and (max-device-width: 480px) {body, input, textarea {font:13pt Helvetica,Calibri,Tahoma}}
@media screen and (min-width:800px) {
  table {float:left; width:32%; margin:0.7%; border:1px solid #e2e2e2; -webkit-box-shadow:1px 1px 5px #ccc}
  .blk table {border:1px solid #333; -webkit-box-shadow:none}
  .group:hover {background:#f4f4f4}
  .blk .group:hover {background:#666}
  #main {overflow:auto}
}
</style>
</head>
<body onload="taskwall.onload();">
<form class="menu" method="get" onsubmit="taskwall.updateView();$('q').blur();return false;">
<span class="group">
<a href="javascript:taskwall.getProjs();"><span id="upd">&dArr;</span>&nbsp;Get</a>
</span>
<span class="group inputbox">
<input type="text" id="q" placeholder="Search" size=15 autocapitalize="off" autocorrect="off" accesskey="s" title="Alt+S">
<a href="javascript:taskwall.updateView();">Go</a>
</span>
<span class="group">
<a href="javascript:taskwall.updateView('@0');" title="0">Today</a>
<a href="javascript:taskwall.updateView('@3');" title="3">Soon</a>
<a href="javascript:taskwall.updateView('');" title="Esc">All</a>
</span>
<span class="group">
<a href="javascript:taskwall.updateView('@*');">&#9733;</a>
<a href="javascript:taskwall.updateView('@#');">&hearts;</a>
</span>
<span id="projects"></span>
</form>
<div id="main"><noscript>Please enable JavaScript to view this site.</noscript></div>
<div class="menu">
<span class="group"><a href="javascript:void($('morelinks').style.display='');">More...</a></span>
<span id="morelinks" style="display:none">
<span class="group"><a href="javascript:taskwall.logout();">Logout</a></span>
<span class="group"><a href="javascript:void(document.body.className=taskwall.cookie('twbg','blk'));">Black</a><a href="javascript:void(document.body.className=taskwall.cookie('twbg',''));">White</a></span>
<span class="group inputbox">Startup <input type="text" id="startq" placeholder="Search" size=8 autocapitalize="off" autocorrect="off" onchange="taskwall.cookie('soon',this.value);"></span>
<span class="group"><a href="http://mail.google.com/tasks/iphone">Google &raquo;</a></span>
<span class="group"><a href="http://code.google.com/p/simpleajax/issues/list">Bugs &raquo;</a></span>
<div style="color:#aaa;text-align:center;margin:1em">Hotkeys: Anywhere type '0' for today's tasks, '1' for tomorrow, or any letter to jump to a Project, then press Enter.</div>
</div>
<h1 id="intro" style="color:#aaa; font-weight:normal; margin:1em 10%; text-align:center; display:none">All your Google Tasks right on your iPhone available offline. Filter by project or date. Instant search. Google ID required. Click 'Get' to start.</h1>
</div>
<script>
var isTouch = document.body;
isTouch.ontouchstart = function() {isTouch=1;};
isTouch.ontouchmove = isTouch.ongesturestart = function() {isTouch=0;};
isTouch.onkeydown = function(e, s, n) {
  if(e.target && e.target.tagName != "INPUT" && e.target.tagName != "TEXTAREA" && !e.ctrlKey && !e.altKey && !e.metaKey) {
    s = String.fromCharCode(e.keyCode), n = $("q");
    if(s >= "0" && s <= "9") {
      n.value = "@";
    } else if(s >= "A" && s <= "Z") {
      n.value = "@~";
    } else if(e.keyCode == 27) {
      return taskwall.updateView("");
    } else {
      return;
    }
    n.focus();
  }
};
if("ontouchend" in document.body) {
  isTouch = document.querySelectorAll("a[href^='javascript:']");
  for(var i=isTouch.length-1; i>=0; i--) {
    isTouch[i].setAttribute("ontouchend", "if(!isTouch)return;isTouch=0;" + isTouch[i].href.replace("javascript:",""));
    isTouch[i].href = "javascript:void(0);";
  }
}
</script>
</body>
</html>